package org.summerframework.component.security.core.service.impl;

import org.summerframework.component.security.Constants;
import org.summerframework.component.security.LoginData;
import org.summerframework.component.security.core.SecurityResultCode;
import org.summerframework.component.security.core.config.LoginProperties;
import org.summerframework.component.security.context.RequestContext;
import org.summerframework.component.security.context.RequestContextFactory;
import org.summerframework.component.security.core.service.*;
import org.summerframework.component.security.core.service.*;
import org.summerframework.component.security.enumeration.LoginAccountEnum;
import org.summerframework.component.security.enumeration.LoginFromEnum;
import org.summerframework.component.security.enumeration.LoginSecurityEnum;
import org.summerframework.component.security.enumeration.LoginTypeEnum;
import org.summerframework.component.security.core.event.LoginEvent;
import org.summerframework.component.security.core.event.LoginPreEvent;
import org.summerframework.component.security.core.role.AuthenticationService;
import org.summerframework.component.security.core.util.useragent.UADeviceJsUtil;
import org.summerframework.component.security.core.util.useragent.UserAgentInfo;
import org.summerframework.core.base.result.DefaultResultCode;
import org.summerframework.core.exception.WebException;
import org.summerframework.core.util.CheckUtil;
import org.summerframework.core.util.SpringContextUtil;
import org.summerframework.core.util.config.ConfigUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.ServletRequestUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * 抽象类配合 {@link LoginManage} 使用
 * <p>
 * <p>
 * 接收参数 {@link LoginData}
 * from: 登录来源 {@link LoginFromEnum}
 * loginType: 登录类型 {@link LoginTypeEnum}
 * json: 是否返回json
 * 0:页面    PS:根据style设置显示模板  {@link ConfigUtils#getProperty(String)} login.page.result.view.{styleValue}
 * 1:json   PS:根据style设置显示模板  {@link ConfigUtils#getProperty(String)}} login.json.result.view.{styleValue}
 * <p>
 * style: 页面风格   PS:配合json使用
 * redirectURL: 重定向页面
 * redirect_url: 重定向页面, 当redirectURL为空时获取, 当还是没有时默认值 {@link ConfigUtils#getProperty(String)}} login.default.redirect_url
 * username: 登录用户名, 自动匹配为手机,邮箱,其他 {@link LoginAccountEnum}
 * callback: jsonp
 * reffer: 未知用处
 * deviceToken: iOS 推送的token值
 * <p>
 * 获取参数
 * ip: 请求地址
 * loginAccount: 登录用户名的方式
 *
 * @author 石超
 * @version v1.0.0
 */
public abstract class AbstractLoginServiceImpl<T> implements LoginService {
    final Logger log;
    @Resource
    protected LoginProperties loginProperties;

    @Resource
    protected UserLoginService<T> userLoginService;

    @Resource
    protected LoginLogService loginLogService;

    @Resource
    protected AuthenticationService authenticationService;

    @Resource
    protected LogoutService logoutService;

    @Resource
    protected SessionService<T> sessionService;

    {
        log = LoggerFactory.getLogger(this.getClass());
    }

    @Override
    public LoginData setLoginContext() {
        HttpServletRequest httpServletRequest = getContext().getRequest();

        LoginData<T> loginData = getLoginData();

        // 1.登录来源（网页，客户端，ipad），决定最后返回的内容。eg:网页登录，成功后重定向到指定的页面，而客户端和ipad登录是直接返回json
        int loginFrom = ServletRequestUtils.getIntParameter(httpServletRequest, Constants.LOGIN_FROM, LoginFromEnum.LOGIN_FROM_WEBSITE.getType());
        loginData.setLoginFromEnum(LoginFromEnum.getByType(loginFrom));

        if (loginData.getLoginFromEnum() == LoginFromEnum.LOGIN_FROM_ERROR) {
            throw new WebException(DefaultResultCode.ILLEGAL_ARGUMENT);
        }

        //设置长登录时间, 永久请设置-1, 默认30天
        if (BooleanUtils.toBoolean(ServletRequestUtils.getStringParameter(httpServletRequest, Constants.REMEMBER_ME, "false"))) {
            loginData.setSessionExpiringTime(loginProperties.getRememberMeTime());
        } else if (loginData.getLoginFromEnum().isForEverSession()) {
            loginData.setSessionExpiringTime(loginProperties.getLongExpiringTime());
        } else {
            loginData.setSessionExpiringTime(loginProperties.getDefaultExpiringTime());
        }

        //session过去时间
        int sessionExpiringTime = ServletRequestUtils.getIntParameter(httpServletRequest, Constants.LOGIN_SESSION_EXPIRING_TIME, loginData.getSessionExpiringTime());

        loginData.setSessionExpiringTime(sessionExpiringTime);

        //是否返回json
        int isJson = ServletRequestUtils.getIntParameter(getContext().getRequest(), Constants.JSON, 0);
        loginData.setJson(isJson);

        // set reffer
        loginData.setDeviceToken(ServletRequestUtils.getStringParameter(httpServletRequest, Constants.DEVICE_TOKEN, null));


        // 3.登录类型
        if (loginData.getLoginType() == null) {
            int loginType = ServletRequestUtils.getIntParameter(httpServletRequest, Constants.LOGIN_TYPE, LoginTypeEnum.NORMAL_FLOW.getType());
            loginData.setLoginType(loginType);
        }

        // style
        if (loginData.getStyle() == null) {
            String Style = ServletRequestUtils.getStringParameter(httpServletRequest, Constants.LOGIN_STYPE, loginProperties.getLoginStyle());
            loginData.setStyle(Style);
        }

        // 设置登录安全类型（HTTP登录，HTTPS登录
        if (!httpServletRequest.isSecure()) {
            loginData.setLoginSecurityEnum(LoginSecurityEnum.HTTP);
        }

        // 设置redirectUrl
        String redirectUrl = ServletRequestUtils.getStringParameter(httpServletRequest, Constants.REDIRECT_URL, loginData.getRedirectUrl());

        if (ClassUtils.isPresent("org.springframework.security.web.savedrequest.SavedRequest", this.getClass().getClassLoader())) {
            SavedRequest savedRequest = (SavedRequest) getContext().getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST");

            if (savedRequest != null) {
                redirectUrl = savedRequest.getRedirectUrl();
            }
        }

        if (StringUtils.isBlank(redirectUrl)) {
            redirectUrl = ServletRequestUtils.getStringParameter(httpServletRequest, Constants.REDIRECT_URL2, "");
        }

        if (StringUtils.isBlank(redirectUrl)) {
            redirectUrl = loginProperties.getRedirectUrl();
        }

        loginData.setRedirectUrl(redirectUrl);

        // 设置来源
        Integer source = ServletRequestUtils.getIntParameter(httpServletRequest, Constants.SOURCE, 0);
        loginData.setSource(source);

        String username = ServletRequestUtils.getStringParameter(httpServletRequest, Constants.USER_NAME_IN_VM, "");

        if (StringUtils.isNotBlank(username)) {
            // 默认支持会员名登录
            loginData.setLoginAccountEnum(LoginAccountEnum.LOGIN_NAME);
            loginData.setUserName(username);

            // 邮箱登录
            if (StringUtils.isNotBlank(username) && CheckUtil.isEmail(username)) {
                loginData.setLoginAccountEnum(LoginAccountEnum.LOGIN_EMAIL);
            }

            // 手机号码登录
            if (StringUtils.isNotBlank(username) && CheckUtil.isMobile(username)) {
                loginData.setLoginAccountEnum(LoginAccountEnum.LOGIN_MOBILE);
            }
        } else if (hasUsername()) {
            throw new WebException(SecurityResultCode.ERROR_USER_PHONE_PROMOTED_TYPE);
        }

        // jsonp参数
        loginData.setCallback(ServletRequestUtils.getStringParameter(httpServletRequest, Constants.CALLBACK, ""));

        loginData.setLoginIp(getContext().getRequestIp());

        authenticationService.preAuthentication(loginData);
        SpringContextUtil.publishEvent(new LoginPreEvent<>(loginData));

        return loginData;
    }

    public LoginData<T> getLoginData() {
        return CURR_LOGIN_DATA.get();
    }

    protected abstract boolean hasUsername();

    @Override
    public void finishLogin() {
        T userDTO = validateLoginContext();

        if (userDTO == null) {
            throw new WebException(DefaultResultCode.ILLEGAL_ARGUMENT, "用户不存在或已删除");
        }

        setLoginDate();
        finishLogin(userDTO);

        // 插入登录日志
        setLoginLog();

        setDate(getContext().getSession());
        updateFailCount(true);
    }

    protected void setLoginDate() {
        LoginData loginData = getContext().getLoginData();

        loginData.clearResult();
        loginData.putResult(getLoginData().getResult());
        loginData.setSessionExpiringTime(getLoginData().getSessionExpiringTime());
        loginData.setJson(getLoginData().getJson());
        loginData.setDeviceToken(getLoginData().getDeviceToken());
        loginData.setStyle(getLoginData().getStyle());
        loginData.setRedirectUrl(getLoginData().getRedirectUrl());
        loginData.setLoginAccountEnum(getLoginData().getLoginAccountEnum());
        loginData.setLoginFromEnum(getLoginData().getLoginFromEnum());
        loginData.setLoginSecurityEnum(getLoginData().getLoginSecurityEnum());
        loginData.setSource(getLoginData().getSource());
    }

    @Override
    public void updateFailCount(boolean loginFailed) {
        LoginData loginData = getContext().getLoginData();

        if (loginData.getUser() == null) {
            return;
        }

        Integer failCount = loginData.getFailCount();

        if (loginFailed) {
            if (failCount <= Constants.MAX_LOGIN_FAIL_COUNT) {
                userLoginService.updateFailCount(true);
            }
        } else {
            userLoginService.updateFailCount(false);
        }
    }

    /**
     * 设置登录信息
     *
     * @param session   session
     * @param loginData 登录信息
     */
    protected void setDate(HttpSession session, LoginData loginData) {
        if (loginData.getLoginFromEnum().isForEverSession()) {
            logoutService.logout(loginData.getUserId(), loginData.getLoginFromEnum().getType(), loginData.getSource());
        }

        session.setAttribute(Constants.SESSION_USERNAME, loginData.getUserName());
        session.setAttribute(Constants.SESSION_USER_ID, loginData.getUserId());
        session.setAttribute(Constants.SESSION_LOGIN_DATE, loginData);
        session.setAttribute(Constants.SESSION_DEVICE_TOKEN, loginData.getDeviceToken());
        sessionService.complete(session, loginData);
        session.setMaxInactiveInterval(loginData.getSessionExpiringTime());
    }

    /**
     * 设置登录信息
     *
     * @param session session
     */
    protected void setDate(HttpSession session) {
        setDate(session, getContext().getLoginData());
    }

    /**
     * 验证登录
     *
     * @return 成功后的用户信息
     */
    protected abstract T validateLoginContext();

    /**
     * 添加登录日志
     */
    void setLoginLog() {
        setLoginLog(getContext().getLoginData());
    }

    /**
     * 添加登录日志
     *
     * @param loginData 登录信息
     */
    void setLoginLog(LoginData<T> loginData) {
        setLoginLog(loginData, UADeviceJsUtil.parse(getContext().getLoginData().getUserAgent()));
    }

    /**
     * 添加登录日志
     *
     * @param loginData     登录信息
     * @param userAgentInfo UA信息
     */
    void setLoginLog(LoginData<T> loginData, UserAgentInfo userAgentInfo) {
        userLoginService.updateLoginLog(loginData);

        if (userAgentInfo != null) {
            loginData.setLoginLogId(loginLogService.insert(loginData, userAgentInfo));
        }
    }

    protected RequestContext<T> getContext() {
        return RequestContextFactory.getCurrentContext();
    }

    /**
     * 完成登录
     * 同步登录信息 {@link LoginData}
     *
     * @param userDTO 用户
     */
    protected void finishLogin(T userDTO) {
        finishLogin(getContext().getLoginData(), userDTO);
    }

    /**
     * 完成登录
     * 同步登录信息 {@link LoginData}
     *
     * @param userDTO 用户
     */
    void finishLogin(LoginData<T> loginData, T userDTO) {
        loginData.setLoginType(getLoginData().getLoginType());
        loginData.setLoginFromEnum(getLoginData().getLoginFromEnum());
        loginData.setJson(getLoginData().getJson());
        loginData.setLoginIp(getLoginData().getLoginIp());
        loginData.setUser(userDTO);
        userLoginService.finishLogin(loginData, userDTO);
        authenticationService.postAuthentication(loginData);
        SpringContextUtil.publishEvent(new LoginEvent<>(loginData));
    }
}
